"""
内容：输入数值多元分类模型示例
日期：2020年7月6日
作者：Howie
"""
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation
from keras.utils import plot_model, to_categorical
import matplotlib.pyplot as plt

# 预设
N_SAMPLES_TRAIN = 1000
N_SAMPLES_TEST = 100
N_FEATURES = 12
EPOCH = 1000
BATCH_SIZE = 64
N_CLASSES = 10


def hist_plot(hist, model_name):
    """
    # 可视化训练过程
    :param hist: history对象
    :return:
    """
    fig, loss_ax = plt.subplots()
    acc_ax = loss_ax.twinx()
    # 每个训练周期的训练误差与验证误差
    loss_ax.plot(hist.history['loss'], 'y', label='Loss')
    # 每个训练周期的训练精度与验证精度
    acc_ax.plot(hist.history['accuracy'], 'b', label='Acc')
    # 横轴与纵轴
    loss_ax.set_xlabel('epoch')
    loss_ax.set_ylabel('loss')
    acc_ax.set_ylabel('accuracy')
    # 标签
    loss_ax.legend(loc='upper left')
    acc_ax.legend(loc='lower left')
    # 标题
    plt.title(model_name)
    # 保存
    plt.savefig('./logs/History_Demo3_' + model_name + '.pdf')
    # 展示
    plt.show()


def dataset_generating(verbose=False):
    """
    # 生成数据集
    :return:
    """
    # 二元分类模型
    X_train = np.random.random(
        (N_SAMPLES_TRAIN, N_FEATURES))  # 1000个用于训练的具有12个任意值的输入值
    Y_train = np.random.randint(N_CLASSES, size=(N_SAMPLES_TRAIN, 1))
    X_test = np.random.random((N_SAMPLES_TEST, N_FEATURES))
    Y_test = np.random.randint(N_CLASSES, size=(N_SAMPLES_TEST, 1))
    # 独热编码: 在多元分类问题中，指定分类概率值时，需要使用独热编码
    Y_train = to_categorical(Y_train, num_classes=N_CLASSES)
    Y_test = to_categorical(Y_test, num_classes=N_CLASSES)
    print(
        "Train Samples: {}, Test Samples: {}".format(
            X_train.shape,
            X_test.shape))

    return X_train, X_test, Y_train, Y_test


def model_building(choice='Multi-Layer Perceptron'):
    model = Sequential()
    if choice == 'Multi-Layer Perceptron':
        model.add(Dense(units=64, input_dim=12))
        model.add(Activation('relu'))
        model.add(Dense(units=N_CLASSES))
        # softmax激活函数:
        # 映射输入值返回不同分类的概率值。所返回的概率值相加之和为1
        # 常用于多元分类问题模型的输出层中，概率值最高的类别的是模型预测得出的分类结果
        model.add(Activation('softmax'))
    elif choice == 'Deep Multi-Layer Perceptron':
        model.add(Dense(units=64, input_dim=12))
        model.add(Activation('relu'))
        model.add(Dense(units=64))
        model.add(Activation('relu'))
        model.add(Dense(units=N_CLASSES))
        model.add(Activation('softmax'))
    else:
        model.add(Dense(units=N_CLASSES, input_dim=12))
        model.add(Activation('softmax'))

    plot_model(
        model,
        to_file='./logs/Demo3_model_' +
        choice +
        '.pdf',
        show_shapes=True)

    return model


def model_training():
    # 生成数据集
    X_train, X_test, Y_train, Y_test = dataset_generating(verbose=True)
    titles = ['Multi-Layer Perceptron',
              'Deep Multi-Layer Perceptron',
              'Perceptron']
    for title in titles:
        # 搭建模型
        model = model_building(choice=title)
        model.compile(
            optimizer='rmsprop',
            loss='categorical_crossentropy',
            metrics=['accuracy'])
        # 训练模型
        hist = model.fit(X_train, Y_train, epochs=EPOCH, batch_size=BATCH_SIZE)
        # 查看训练过程
        hist_plot(hist, model_name=title)
        # 评价模型
        loss_and_metrics = model.evaluate(
            X_test, Y_test, batch_size=BATCH_SIZE // 2)
        print("Loss and metrics: {}".format(loss_and_metrics))


if __name__ == '__main__':
    model_training()
